import dataclasses
import time
import uuid

from marshmallow import Schema, fields

from webutils.marshmallow.fields import IntBool
from webutils.webargs.bare import use_args
from webutils.webargs.tornado import parser
from webutils.tornado.responses import Response

from message_broker.const import ActType, UserGroup
from message_broker.websocket import (BrokerHandler, mixin_chat, mixin_state,
                                      mixin_handwriting)
from message_broker.services import ProxyUserService

from .. import const, params, services


@dataclasses.dataclass
class UserLoginMessage:
    uid: str
    nm: str
    idt: int
    hw: int = 0
    sex: str = '0'
    vi: str = '0'
    act: str = str(ActType.USER_ONLINE.value)


@dataclasses.dataclass
class DeviceLoginMessage:
    did: str
    nm: str
    hw: int = 0
    act: str = str(ActType.DEVICE_ONLINE.value)


@dataclasses.dataclass
class UserInfo:
    identity: int
    sex: str = '0'
    avatar: str = ''
    type: int = 0


@dataclasses.dataclass
class SessionInfo:
    id: int
    name: str
    user_info: UserInfo = None
    online_at: int = dataclasses.field(
        default_factory=lambda: int(time.time()))
    classroom: str = '0'
    handwrite: int = 0
    videointeract: int = 0
    type: int = 0


class AuthrizeForm(Schema):
    token = params.Token(required=True)
    multi = fields.Bool(missing=True)


class WebSocketHandler(mixin_chat.ChatMixin, mixin_state.StateMixin,
                       mixin_handwriting.HandwritingMixin, BrokerHandler):
    def check_origin(self, *args):
        return True

    async def authorize(self, conn, unit_id=None):
        self.session.unit_id = unit_id
        self.session.room_id = unit_id
        if not self.get_argument('token', None):
            msg = 'The access token miss'
            resp = Response(errcode=1, errmsg=msg)
            await self.write_response(resp)
            self.abort('[authorize] The access token miss')
        args = parser.parse(AuthrizeForm, self.request, location='query')

        # 验证token
        user_service = services.UserService(conn, room_id=self.session.room_id)
        session_type, user = await user_service.get_user_info(args['token'])
        if not user:
            msg = 'The access token provided is invalid'
            resp = Response(errcode=1, errmsg=msg)
            await self.write_response(resp)
            self.abort('[authorize] token provided is invalid')
        elif user == 'device_error':
            msg = 'The device token not device_type or classroom_id info'
            resp = Response(errcode=1, errmsg=msg)
            await self.write_response(resp)
            self.abort('[authorize] device token not config[classroom_id]')
        # 验证单元信息
        unit_service = services.UnitService(conn, unit_id=self.session.unit_id)
        unit_info = await unit_service.get_unit_info()
        if not unit_info:
            resp = Response(errcode=500, errmsg='unit does not exist')
            await self.write_response(resp)
            self.abort('[authorize] unit does not exist')
        if unit_info.status is const.UnitStatus.FINISHED:
            msg = 'failed to enter the course unit, the unit is end.'
            resp = Response(errcode=500, errmsg=msg)
            await self.write_response(resp)
            self.abort('[authorize] unit is finished')
        self.session.course_id = unit_info.course_id
        # 获取用户身份
        if session_type is const.SessionType.USER:
            role = await user_service.get_user_role(args['token'], unit_info,
                                                    user.id)
            user.role = role

        # 初始化
        session_id = user.id  # 有可能是用户id/设备id
        if args['multi']:
            session_id += '-' + uuid.uuid4().hex
        self.session.id = session_id
        if session_type is const.SessionType.USER:
            self.session.user = user
        else:
            self.session.agent = user

    async def on_act_register(self, args, conn):
        if args.get('did'):  # 代理设备注册
            if self.is_proxy_mode:
                await register_remote_device(self, args, conn)
            else:
                msg = 'supposed to register as user'
                resp = Response(errcode=500, errmsg=msg)
                await self.write_response(resp)
                self.abort(f'[on_act_register] {msg}')
        else:
            # 用户直接注册
            if self.is_proxy_mode:
                msg = 'supposed to register as device'
                resp = Response(errcode=500, errmsg=msg)
                await self.write_response(resp)
                self.abort(f'[on_act_register] {msg}')
            await register_remote_user(self, args, conn)

        self.session.is_registered = True
        resp = Response(
            type='21',
            act=str(ActType.MESSAGE_ACK.value),
            stat='1',
        )
        await self.write_response(resp)

    async def on_act_local_register(self, args, conn):
        # 地备注册 广播
        if args.get('usr'):
            for user_info in args['usr']:
                await local_register_remote_user(self, user_info, conn)
        if args.get('dev'):
            for device_info in args['dev']:
                await local_register_remote_device(self, device_info, conn)

        self.session.is_registered = True
        resp = Response(
            type='21',
            act=str(ActType.MESSAGE_ACK.value),
            stat=1,
        )
        await self.write_response(resp)

    async def on_act_batch_register(self, args, conn):
        # 本地备注册 广播
        # await register_remote_device(self, args, conn)
        proxy_user_service = \
            ProxyUserService(conn,
                             room_id=self.session.room_id,
                             proxy_id=self.session.id)
        await proxy_user_service.clear()
        if args.get('usr'):
            for i in args['usr']:
                await local_register_remote_user(self, i, conn)
        if args.get('dev'):
            for i in args['dev']:
                await local_register_remote_device(self, i, conn)

        self.session.is_registered = True
        resp = Response(
            type='21',
            act=str(ActType.MESSAGE_ACK.value),
            stat=1,
        )
        await self.write_response(resp)

    async def on_act_user_offline(self, args, conn):
        # 用户下线
        self.logger.info(f'[on_act_user_offline]--{args=}')


@use_args({
    'vi': IntBool(required=False, missing=False),
    'hw': IntBool(required=False, misssing=False),
})
async def register_remote_user(ws, args, conn):
    identity = const.SessionRole.from_user_role(ws.session.user.role)
    session_info = SessionInfo(
        id=ws.session.user.id,
        name=ws.session.user.name,
        handwrite=int(args['hw']),
        videointeract=int(args['hw']),
        user_info=UserInfo(identity=identity.value, ),
    )
    user_service = services.UserService(conn, room_id=ws.session.room_id)
    await user_service.login_user(
        ws.session.user.id,
        ws.session.user_group,
        dataclasses.asdict(session_info),
    )
    login_message = UserLoginMessage(
        hw=1,
        idt=identity.value,
        uid=ws.session.user.id,
        nm=ws.session.user.name,
        vi=str(int(args['vi'])),
    )
    await ws.broadcaster_app.send_task(
        'broadcast_login',
        dataclasses.asdict(login_message),
        group=UserGroup.ALL,
    )


@use_args({
    'vi': IntBool(required=False, missing=False),
    'hw': IntBool(required=False, misssing=False),
})
async def register_remote_device(ws, args, conn):
    identity = const.SessionRole.from_device_type(ws.session.agent.type)
    session_info = SessionInfo(
        id=ws.session.agent.id,
        name=ws.session.agent.id,
        type=identity.value
    )
    user_service = services.UserService(conn, room_id=ws.session.room_id)
    await user_service.login_user(ws.session.agent.id, UserGroup.DEVICE,
                                  dataclasses.asdict(session_info))
    login_message = DeviceLoginMessage(
        did=ws.session.agent.id,
        nm=ws.session.agent.id,
    )
    await ws.broadcaster_app.send_task(
        'broadcast_login',
        dataclasses.asdict(login_message),
        group=UserGroup.ALL,
    )


@use_args({
    'uid': fields.Str(required=True),
    'usr': fields.Str(required=False),
    'idt': fields.Str(required=False, missing='2'),  # 身份。1：教师 2：学员
    'nm': fields.Str(required=False, missing='visitor'),
    'os': fields.Str(required=False, missing='6'),
    'idts': fields.Str(required=False, missing='2'),
    'avatar': fields.Str(required=False, missing=''),
    'vi': IntBool(required=False, missing=False),
    'hw': IntBool(required=False, misssing=False),
})
async def local_register_remote_user(ws, args, conn):
    identity = args['idt']
    session_info = SessionInfo(
        id=args['uid'],
        name=args['nm'],
        handwrite=int(args.get('hw', 0)),
        videointeract=int(args.get('vi', 0)),
        user_info=UserInfo(identity=identity, ),
    )
    # 1.login_user
    user_service = services.UserService(conn, room_id=ws.session.room_id)
    await user_service.login_user(
        args['uid'],
        ws.session.user_group,
        dataclasses.asdict(session_info),
    )
    # 2. local proxy cache add_user
    proxy_user_service = ProxyUserService(conn,
                                          room_id=ws.session.room_id,
                                          proxy_id=ws.session.agent.id)
    await proxy_user_service.add_user(args['uid'])

    # 3. 广播上线通知
    login_message = UserLoginMessage(
        hw=1,
        idt=identity,
        uid=args['uid'],
        nm=args['nm'],
        vi=str(int(args['vi'])),
    )
    await ws.broadcaster_app.send_task(
        'broadcast_login',
        dataclasses.asdict(login_message),
        group=UserGroup.ALL,
    )


@use_args({
    'did': fields.Str(required=True),
    'dt': fields.Str(required=True),
    'nm': fields.Str(required=True),
    'vi': IntBool(required=False, missing=False),
    'hw': IntBool(required=False, misssing=False),
})
async def local_register_remote_device(ws, args, conn):
    identity = const.SessionRole.from_device_type(ws.session.agent.type)
    session_info = SessionInfo(
        id=args['did'],
        name=args['nm'],
        handwrite=int(args.get('hw', 0)),
        videointeract=int(args.get('vi', 0)),
        type=identity.value
    )
    # 1.login_user
    user_service = services.UserService(conn, room_id=ws.session.room_id)
    await user_service.login_user(
        args['did'],
        UserGroup.DEVICE,
        dataclasses.asdict(session_info),
    )
    # 2. local proxy cache add_user
    proxy_user_service = ProxyUserService(conn,
                                          room_id=ws.session.room_id,
                                          proxy_id=ws.session.agent.id)
    await proxy_user_service.add_user(args['did'])

    # 3. 广播上线通知
    login_message = DeviceLoginMessage(
        did=ws.session.agent.id,
        nm=ws.session.agent.id,
    )
    await ws.broadcaster_app.send_task(
        'broadcast_login',
        dataclasses.asdict(login_message),
        group=UserGroup.ALL,
    )
